angular.module('leaflet-directive').directive('paths', function($log, $q, leafletData, leafletMapDefaults, leafletHelpers, leafletPathsHelpers, leafletPathEvents) {

  return {
    restrict: 'A',
    scope: false,
    replace: false,
    require: ['leaflet', '?layers'],

    link: function(scope, element, attrs, controller) {
      var mapController = controller[0];
      var isDefined = leafletHelpers.isDefined;
      var isString = leafletHelpers.isString;
      var leafletScope  = mapController.getLeafletScope();
      var paths     = leafletScope.paths;
      var createPath = leafletPathsHelpers.createPath;
      var bindPathEvents = leafletPathEvents.bindPathEvents;
      var setPathOptions = leafletPathsHelpers.setPathOptions;

      mapController.getMap().then(function(map) {
        var defaults = leafletMapDefaults.getDefaults(attrs.id);
        var getLayers;

        // If the layers attribute is used, we must wait until the layers are created
        if (isDefined(controller[1])) {
          getLayers = controller[1].getLayers;
        } else {
          getLayers = function() {
            var deferred = $q.defer();
            deferred.resolve();
            return deferred.promise;
          };
        }

        if (!isDefined(paths)) {
          return;
        }

        getLayers().then(function(layers) {

          var leafletPaths = {};
          leafletData.setPaths(leafletPaths, attrs.id);

          // Should we watch for every specific marker on the map?
          var shouldWatch = (!isDefined(attrs.watchPaths) || attrs.watchPaths === 'true');

          // Function for listening every single path once created
          var watchPathFn = function(leafletPath, name) {
            var clearWatch = leafletScope.$watch('paths["' + name + '"]', function(pathData, old) {
              if (!isDefined(pathData)) {
                if (isDefined(old.layer)) {
                  for (var i in layers.overlays) {
                    var overlay = layers.overlays[i];
                    overlay.removeLayer(leafletPath);
                  }
                }

                map.removeLayer(leafletPath);
                clearWatch();
                return;
              }

              setPathOptions(leafletPath, pathData.type, pathData);
            }, true);
          };

          leafletScope.$watchCollection('paths', function(newPaths) {

            // Delete paths (by name) from the array
            for (var name in leafletPaths) {
              if (!isDefined(newPaths[name])) {
                map.removeLayer(leafletPaths[name]);
                delete leafletPaths[name];
              }
            }

            // Create the new paths
            for (var newName in newPaths) {
              if (newName.search('\\$') === 0) {
                continue;
              }

              if (newName.search('-') !== -1) {
                $log.error('[AngularJS - Leaflet] The path name "' + newName + '" is not valid. It must not include "-" and a number.');
                continue;
              }

              if (!isDefined(leafletPaths[newName])) {
                var pathData = newPaths[newName];
                var newPath = createPath(newName, newPaths[newName], defaults);

                // bind popup if defined
                if (isDefined(newPath) && isDefined(pathData.message)) {
                  newPath.bindPopup(pathData.message, pathData.popupOptions);
                }

                // Show label if defined
                if (leafletHelpers.LabelPlugin.isLoaded() && isDefined(pathData.label) && isDefined(pathData.label.message)) {
                  newPath.bindLabel(pathData.label.message, pathData.label.options);
                }

                // Check if the marker should be added to a layer
                if (isDefined(pathData) && isDefined(pathData.layer)) {

                  if (!isString(pathData.layer)) {
                    $log.error('[AngularJS - Leaflet] A layername must be a string');
                    continue;
                  }

                  if (!isDefined(layers)) {
                    $log.error('[AngularJS - Leaflet] You must add layers to the directive if the markers are going to use this functionality.');
                    continue;
                  }

                  if (!isDefined(layers.overlays) || !isDefined(layers.overlays[pathData.layer])) {
                    $log.error('[AngularJS - Leaflet] A path can only be added to a layer of type "group"');
                    continue;
                  }

                  var layerGroup = layers.overlays[pathData.layer];
                  if (!(layerGroup instanceof L.LayerGroup || layerGroup instanceof L.FeatureGroup)) {
                    $log.error('[AngularJS - Leaflet] Adding a path to an overlay needs a overlay of the type "group" or "featureGroup"');
                    continue;
                  }

                  // Listen for changes on the new path
                  leafletPaths[newName] = newPath;

                  // The path goes to a correct layer group, so first of all we add it
                  layerGroup.addLayer(newPath);

                  if (shouldWatch) {
                    watchPathFn(newPath, newName);
                  } else {
                    setPathOptions(newPath, pathData.type, pathData);
                  }
                } else if (isDefined(newPath)) {
                  // Listen for changes on the new path
                  leafletPaths[newName] = newPath;
                  map.addLayer(newPath);

                  if (shouldWatch) {
                    watchPathFn(newPath, newName);
                  } else {
                    setPathOptions(newPath, pathData.type, pathData);
                  }
                }

                bindPathEvents(attrs.id, newPath, newName, pathData, leafletScope);
              }
            }
          });
        });
      });
    },
  };
});
